﻿/**
 *  该工具类拷贝自SLaB(http://www.davidpoll.com/downloads-and-samples/#SLaB) 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Resources;

namespace Aspire
{
    /// <summary>
    /// 工具类，用来加载Xap包，并组合其中的缓存程序集
    /// </summary>
    public class XapLoader
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public XapLoader()
        {
        }

        /// <summary>
        /// 从给定的Uri异步加载xap包
        /// </summary>
        /// <param name="uri">XAP包的URI地址</param>
        /// <param name="callback">操作结束后执行的回掉函数.</param>
        /// <param name="asyncState">Custom state to be carried with the async operation.</param>
        /// <returns>A XapAsyncResult that can be used to retrieve the loaded Xap.</returns>
        public IAsyncResult BeginLoadXap(Uri uri, AsyncCallback callback, object asyncState)
        {
            WebClient wc = new WebClient();
            XapAsyncResult result = new XapAsyncResult(asyncState);
            wc.OpenReadCompleted += (sender, args) =>
                {
                    try
                    {
                        if (args.Error != null)
                            throw args.Error;
                        Stream response = args.Result;
                        XapAsyncResult xapResult = (XapAsyncResult)BeginLoadXap(response, (res) =>
                        {
                            try
                            {
                                Xap xap = EndLoadXap(res);
                            }
                            catch (Exception e)
                            {
                                result.Error = e;
                                callback(result);
                                return;
                            }
                            callback(result);
                        }, result, uri);
                    }
                    catch (Exception e)
                    {
                        result.Error = e;
                        callback(result);
                        return;
                    }
                };
            result.AddClient(wc);
            wc.OpenReadAsync(uri);
            return result;
        }

        /// <summary>
        /// Completes an asynchronous load of a Xap.
        /// </summary>
        /// <param name="result">The XapAsyncResult that was returned by BeginLoadXap (or passed into the callback).</param>
        /// <returns>The loaded Xap.</returns>
        public Xap EndLoadXap(IAsyncResult result)
        {
            XapAsyncResult res = (XapAsyncResult)result;
            if (res.Error != null)
                throw new Exception("A failure in loading a xap occurred", res.Error);
            return res.Result;
        }

        /// <summary>
        /// Cancels loading of a Xap.
        /// </summary>
        /// <param name="result">The XapAsyncResult that was returned by BeginLoadXap.</param>
        public void CancelLoadXap(IAsyncResult result)
        {
            XapAsyncResult res = (XapAsyncResult)result;
            res.IsCancelled = true;
            foreach (var client in res.ActiveClients)
            {
                client.CancelAsync();
            }
        }

        /// <summary>
        /// Asynchronously loads a Xap from the given stream.
        /// </summary>
        /// <param name="xapStream">The stream from which to load the Xap.</param>
        /// <param name="callback">The callback to be invoked when the operation completes.</param>
        /// <param name="asyncState">Custom state to be carried with the async operation.</param>
        /// <returns>A XapAsyncResult that can be used to retrieve the loaded Xap.</returns>
        public IAsyncResult BeginLoadXap(Stream xapStream, AsyncCallback callback, object asyncState)
        {
            return BeginLoadXap(xapStream, callback, new XapAsyncResult(asyncState), Application.Current.Host.Source);
        }
        private byte[] ReadStream(Stream stream)
        {
            byte[] buffer = new byte[stream.Length];
            stream.Read(buffer, 0, (int)stream.Length);
            return buffer;
        }
        private IAsyncResult BeginLoadXap(Stream xapStream, AsyncCallback callback, XapAsyncResult result, Uri baseUri)
        {
            if (result.IsCancelled)
                return result;
            byte[] bytes = ReadStream(xapStream);
            xapStream = new MemoryStream(bytes);
            StreamResourceInfo sri = new StreamResourceInfo(xapStream, null);
            StreamResourceInfo manifestStream = Application.GetResourceStream(sri, new Uri("AppManifest.xaml", UriKind.Relative));
            Manifest manifest = GetManifest(manifestStream.Stream);
            List<Assembly> assemblies = new List<Assembly>();
            IEnumerable<Uri> externalAssemblies = null;
            Dictionary<Assembly, byte[]> byteMappings = new Dictionary<Assembly, byte[]>();
            if (result.IsCancelled)
                return result;
            UiUtilities.ExecuteOnUiThread(() =>
                {
                    foreach (var asm in manifest.Parts)
                    {
                        StreamResourceInfo assemblyStream = Application.GetResourceStream(sri, new Uri(asm.Source, UriKind.Relative));
                        Assembly asmReflect = asm.Load(assemblyStream.Stream);
                        assemblies.Add(asmReflect);
                        byteMappings[asmReflect] = bytes;
                    }
                    externalAssemblies = (from p in manifest.ExternalParts
                                          where p is ExtensionPart
                                          select ((ExtensionPart)p).Source).ToList();
                });
            if (result.IsCancelled)
                return result;
            if (externalAssemblies.Count() > 0)
            {
                result.CompletedSynchronously = false;
                lock (result.InternalLock) result.ToDownload = externalAssemblies.Count();
            }
            else
            {
                Xap xap = new Xap(manifest, assemblies, byteMappings);
                result.Result = xap;
                (result.AsyncWaitHandle as ManualResetEvent).Set();
                callback(result);
                return result;
            }
            foreach (var uri in externalAssemblies)
            {
                if (result.IsCancelled)
                    return result;
                Uri realUri = uri;
                if (!uri.IsAbsoluteUri)
                    realUri = new Uri(baseUri, uri);
                WebClient wc = new WebClient();
                wc.OpenReadCompleted += (sender, args) =>
                    {
                        try
                        {
                            string assemblyName = realUri.LocalPath.Substring(realUri.LocalPath.LastIndexOf("/") + 1);
                            assemblyName = assemblyName.Substring(0, assemblyName.LastIndexOf(".")) + ".dll";
                            Stream downloadResult = args.Result;
                            byte[] extBytes = ReadStream(downloadResult);
                            downloadResult = new MemoryStream(extBytes);
                            StreamResourceInfo externalSri = new StreamResourceInfo(downloadResult, null);
                            StreamResourceInfo externalAssemblyStream = Application.GetResourceStream(externalSri, new Uri(assemblyName, UriKind.Relative));

                            Assembly asmReflect = UiUtilities.ExecuteOnUiThread(() => new AssemblyPart().Load(externalAssemblyStream.Stream));
                            assemblies.Add(asmReflect);
                            byteMappings[asmReflect] = extBytes;
                        }
                        catch (Exception e)
                        {
                            result.Error = e;
                            callback(result);
                            return;
                        }
                        lock (result.InternalLock)
                        {
                            result.ToDownload--;
                            if (result.ToDownload == 0)
                            {
                                Xap xap = new Xap(manifest, assemblies, byteMappings);
                                result.Result = xap;
                                (result.AsyncWaitHandle as ManualResetEvent).Set();
                                callback(result);
                            }
                        }
                    };
                result.AddClient(wc);
                if (result.IsCancelled)
                    return result;
                wc.OpenReadAsync(realUri);
            }
            return result;
        }

        private Manifest GetManifest(Stream manifestStream)
        {
            StreamReader manifestReader = new StreamReader(manifestStream);
            string manifestXaml = manifestReader.ReadToEnd();
            manifestXaml = manifestXaml.Replace("<Deployment", "<Aspire:Manifest");
            manifestXaml = manifestXaml.Replace("</Deployment", "</Aspire:Manifest");
            manifestXaml = manifestXaml.Replace("xmlns=\"http://schemas.microsoft.com/client/2007/deployment\"",
                "xmlns=\"http://schemas.microsoft.com/client/2007/deployment\" xmlns:Aspire=\"clr-namespace:Aspire;assembly=Aspire\"");
            return UiUtilities.ExecuteOnUiThread(() => (Manifest)XamlReader.Load(manifestXaml));
        }

        /// <summary>
        /// An AsyncResult used when loading Xaps.
        /// </summary>
        public class XapAsyncResult : IAsyncResult, INotifyPropertyChanged
        {
            private ObservableCollection<WebClient> clients;
            private ReadOnlyObservableCollection<WebClient> readOnlyClients;
            private Dictionary<WebClient, DownloadProgressChangedEventArgs> currentProgress;
            internal XapAsyncResult(object asyncState)
            {
                this.AsyncState = asyncState;
                this.AsyncWaitHandle = new ManualResetEvent(false);
                this.InternalLock = new object();
                progress = 0;
                CompletedSynchronously = true;
                clients = new ObservableCollection<WebClient>();
                readOnlyClients = new ReadOnlyObservableCollection<WebClient>(clients);
                currentProgress = new Dictionary<WebClient, DownloadProgressChangedEventArgs>();
            }

            internal bool IsCancelled
            {
                get;
                set;
            }

            /// <summary>
            /// Gets the AsyncState passed into BeginLoadXap.
            /// </summary>
            public object AsyncState
            {
                get;
                private set;
            }

            /// <summary>
            /// Gets a WaitHandle that will be set when the operation completes.
            /// </summary>
            public System.Threading.WaitHandle AsyncWaitHandle
            {
                get;
                private set;
            }

            /// <summary>
            /// Gets whether the operation completed synchronously.
            /// </summary>
            public bool CompletedSynchronously
            {
                get;
                internal set;
            }

            /// <summary>
            /// Gets whether the operation has completed.
            /// </summary>
            public bool IsCompleted
            {
                get
                {
                    return ToDownload == 0 && Result != null;
                }
            }

            private void CheckCompleted()
            {
                if (IsCompleted)
                {
                    ((ManualResetEvent)this.AsyncWaitHandle).Set();
                }
            }

            private double progress;
            /// <summary>
            /// Gets the download progress (in terms of bytes downloaded/known total size).
            /// </summary>
            public double Progress
            {
                get
                {
                    return progress;
                }
                internal set
                {
                    if (value != progress)
                    {
                        progress = value;
                        if (PropertyChanged != null)
                            PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
                    }
                }
            }

            internal object InternalLock { get; private set; }

            private int toDownload;
            internal int ToDownload
            {
                get
                {
                    return toDownload;
                }
                set
                {
                    toDownload = value;
                    CheckCompleted();
                }
            }
            internal Exception Error { get; set; }

            internal void AddClient(WebClient client)
            {
                clients.Add(client);
                client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
            }

            private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
            {
                currentProgress[sender as WebClient] = e;
                if (e.BytesReceived == e.TotalBytesToReceive)
                {
                    (sender as WebClient).DownloadProgressChanged -= client_DownloadProgressChanged;
                }
                UpdateProgress();
            }

            private void UpdateProgress()
            {
                long total = (from client in clients
                              where currentProgress.ContainsKey(client)
                              select currentProgress[client].TotalBytesToReceive).Sum();
                long soFar = (from client in clients
                              where currentProgress.ContainsKey(client)
                              select currentProgress[client].BytesReceived).Sum();
                Progress = 1.0 * soFar / total;
            }

            private Xap result;
            internal Xap Result
            {
                get
                {
                    return result;
                }
                set
                {
                    result = value;
                    CheckCompleted();
                }
            }

            /// <summary>
            /// Gets the set of active WebClients that were used to download the Xap and its dependencies.
            /// </summary>
            public ReadOnlyObservableCollection<WebClient> ActiveClients
            {
                get
                {
                    return readOnlyClients;
                }
            }

            /// <summary>
            /// An event that is raised when Progress changes.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
        }

    }
}
